#include "Level.h"
#include "GameObject.h"
#include "Components/AIComponent.h"
#include "Components/Graphics/SceneNode.h"
#include "Components/Graphics/SpriteNode.h"
#include "Components/SimpleBody.h"
#include "Components/Landscape.h"
#include "Components/LandscapeSceneNode.h"
#include "Components/MapObstacle.h"
#include "Components/Physics/Box2DWorld.h"
#include "Components/Actuators/Vehicle.h"
#include "Components/Sensors/Vision.h"

#include <ECS/Containers/TiledArray.h>
#include "ECS/Palette.h"
#include "Gameplay/Fraction.h"
//#include "System/XMLPaletteParser.h"
#include "Components/Inventory/ItemComponent.h"
#include "Components/Inventory/InventoryComponent.h"
#include "Components/Destroyable.h"
#include "ECS/XML/XmlPaletteLoader.h"
#include "System/Resources.h"

namespace impl
{
	ECS::Palette* _palette = new ECS::Palette();

	void FillPalette()
	{
		GameObject::_metaTable();
		auto toolkit = new Item("toolkit01");

		ECS::XMLPaletteLoader::registerClass<SceneNode, SceneNodeDef>();
		ECS::XMLPaletteLoader::registerClass<SpriteNode, SpriteNodeDef>();
		ECS::XMLPaletteLoader::registerClass<Sensor, SensorDef>();
		ECS::XMLPaletteLoader::registerClass<Vision, VisionDef>();
		ECS::XMLPaletteLoader::registerClass<MapObstacle, MapObstacleDef>();
		ECS::XMLPaletteLoader::registerClass<Actuator, ActuatorDef>();
		ECS::XMLPaletteLoader::registerClass<Vehicle, VehicleDef>();
		ECS::XMLPaletteLoader::registerClass<AIComponent, AIComponentDef>();
		ECS::XMLPaletteLoader::registerClass<ItemComponent, ItemComponentDef>();
		ECS::XMLPaletteLoader::registerClass<InventoryComponent, InventoryComponentDef>();
		ECS::XMLPaletteLoader::registerClass<Destroyable, DestroyableDef>();
		ECS::XMLPaletteLoader::registerClass<Transform, TransformDef>();

		uint8_t* buf = Resources::Load("objects.xml");
		ECS::XMLPaletteLoader::Load(_palette, buf);
		if (buf)
		{
			delete buf;
			buf = nullptr;
		}
	}

	Level* currentLevel = nullptr;

	class GOUpdater : public cocos2d::CCObject
	{
	public:
		GOUpdater(std::vector<GameObject*>* objects) :
		  _objects(objects)
		{
		}

		virtual void update(float dt) override
		{
			for (size_t i = 0; i < _objects->size();)
			{
				ecsSEND_MESSAGE((*_objects)[i], onUpdate);
				if ((*_objects)[i]->isDestroyed())
				{
					(*_objects)[i]->destroyImmediately();
					_objects->erase(_objects->begin() + i);
				}
				else
					++i;
			}
		}
	protected:
		std::vector<GameObject*>* _objects;
	};
}

#include "TinyXML/tinyxml.h"

class XmlLevelLoader
{
public:
	static void Load(LevelDef* def, ECS::Palette* palette, uint8_t* data)
	{
		TiXmlDocument document;
		document.Parse((char*)data);

		TiXmlElement* root = document.RootElement();
		TiXmlElement* objectElement = root->FirstChildElement();
		while (objectElement)
		{
			std::string paletteId = objectElement->Attribute("paletteId");

			ECS::AssemblyDef* t = palette->getDef(paletteId);
			ECS::AssemblyDef* assembly = t->clone();
			PatchComponents(assembly, objectElement);
			def->objects.push_back(assembly);

			objectElement = objectElement->NextSiblingElement();
		}
	}

	static void PatchComponents(ECS::AssemblyDef* assembly, TiXmlElement* element)
	{
		TiXmlElement* componentElement = element->FirstChildElement();
		while (componentElement != nullptr)
		{
			auto componentDef = ECS::XMLPaletteLoader::ParseComponent(componentElement);
			assembly->addOrMerge(componentDef);
			componentElement = componentElement->NextSiblingElement();
		}
	}
};

Level::Level()
{
	impl::currentLevel = this;
}

Level::~Level()
{
}

cocos2d::CCLayer* staticLayer;

void Level::initWorld( cocos2d::CCLayer* _layer )
{
	staticLayer = _layer;
	ECS::TiledArray<int> map;
	map.resize(-5, -5, 5, 5);

	for (auto it = map.begin(); it.isValid(); ++it)
	{
		*it = 4;
	}

	cocos2d::CCDirector::sharedDirector()->getScheduler()->scheduleUpdateForTarget(new impl::GOUpdater(&_objects), 0, false);

	impl::FillPalette();

	/// Create landscape at first
	_layer->addChild(createLandscape()->getComponent<LandscapeSceneNode>()->getNode());

	//// Loading from file
	LevelDef* def = new LevelDef();

	uint8_t* buf = Resources::Load("level.xml");
	XmlLevelLoader::Load(def, impl::_palette, buf);
	initWith(def);
	if (buf)
	{
		delete buf;
		buf = nullptr;
	}
	/// Manual:

	//_layer->addChild(createCrate(1.0f, 5.0f)->getComponent<SceneNode>()->getNode());
	//_layer->addChild(createCrate(2.0f, 4.0f)->getComponent<SceneNode>()->getNode());
	//_layer->addChild(createTree(3.0f, 0.0f)->getComponent<SceneNode>()->getNode());
	//_layer->addChild(createTree(3.0f, 3.0f)->getComponent<SceneNode>()->getNode());
	//_layer->addChild(createTree(2.0f, 4.0f)->getComponent<SceneNode>()->getNode());
	//_layer->addChild(createTank(1, 0.0f, 0.0f)->getComponent<SceneNode>()->getNode());
//	_layer->addChild(createTank(2, 4.0f, 4.0f)->getComponent<SceneNode>()->getNode());
	//_layer->addChild(createToolkit(-1.0f, -1.0f)->getComponent<SceneNode>()->getNode());
	//_layer->addChild(createToolkit(1.0f, 3.0f)->getComponent<SceneNode>()->getNode());
	//_layer->addChild(createToolkit(-3.0f, -2.0f)->getComponent<SceneNode>()->getNode());
	//_layer->addChild(createCrate(10.0f, 4.0f)->getComponent<SceneNode>()->getNode());
}

GameObject* Level::createToolkit(float x, float y)
{
	auto go = impl::_palette->create<GameObject>("toolkit01");
	_objects.push_back(go);

	ecsSEND_MESSAGE(go, onStart);

	go->getComponent<Transform>()->setPosition(x, y);

	return go;
}

GameObject* Level::createCrate( float x /*= 0*/, float y /*= 0*/ )
{
	SpriteNodeDef spriteDef;
	spriteDef.scale = 1.0f;
	spriteDef.sprite = "crate01.png";

	SimpleBodyDef bodyDef;
	bodyDef.type = b2_dynamicBody;
	auto fixture = bodyDef.addBox(1.0f, 1.0f);
	fixture->density = 1.0f;

	GameObject* go = new GameObject();
	_objects.push_back(go);

	auto sceneNode = go->createComponent<SceneNode>();
	auto body = go->createComponent<SimpleBody>();

	sceneNode->initWith(&spriteDef);
	body->initWith(&bodyDef);
	ecsSEND_MESSAGE(go, onStart);

	go->getComponent<Transform>()->setPosition(x, y);
	return go;
}

Level* Level::getCurrentLevel()
{
	return impl::currentLevel;
}

GameObject* Level::createLandscape()
{
	LandscapeDef landscapeDef;
	landscapeDef.tiles.resize(-5, -5, 5, 5);
	for (auto it = landscapeDef.tiles.begin(); it.isValid(); ++it)
	{
		it->gid = 0;
	}

	LandscapeSceneNodeDef sceneNodeDef;
	sceneNodeDef.tiles.resize(-5, -5, 5, 5);
	sceneNodeDef.tileSprites.push_back("grass01.png");
	sceneNodeDef.tileSprites.push_back("sand01.png");
	sceneNodeDef.tileSprites.push_back("rock01.png");
	for (auto it = sceneNodeDef.tiles.begin(); it.isValid(); ++it)
	{
		*it = rand() % 3;
	}

	Box2DWorldDef worldDef;


	GameObject* go = new GameObject();
	_objects.push_back(go);

	auto map = go->createComponent<Landscape>();
	auto sceneNode = go->createComponent<LandscapeSceneNode>();
	auto world = go->createComponent<Box2DWorld>();

	map->initWith(&landscapeDef);
	sceneNode->initWith(&sceneNodeDef);
	world->initWith(&worldDef);

	ecsSEND_MESSAGE(go, onStart);

	return go;
}

GameObject* Level::createTank(int fraction, float x /*= 0*/, float y /*= 0*/ )
{
	auto go = impl::_palette->create<GameObject>("tank01");
	_objects.push_back(go);

	ecsSEND_MESSAGE(go, onStart);

	go->getComponent<Transform>()->setPosition(x, y);
	go->getComponent<AIComponent>()->setFraction(Fraction::get(fraction));

//	go->getComponent<Vehicle>()->setTarget(ECS::Vec2<float>(4.0f, 4.0f));
	return go;
}

GameObject* Level::createTree( float x /*= 0*/, float y /*= 0*/ )
{
	auto go = impl::_palette->create<GameObject>("tree01");

	ecsSEND_MESSAGE(go, onStart);

	go->getComponent<Transform>()->setPosition(x, y);
	return go;
}

bool Level::initWith( LevelDef* def )
{
	for (auto it = def->objects.begin(); it != def->objects.end(); ++it)
	{
		ECS::AssemblyDef* def = *it;
		GameObject* go = (GameObject*)def->create();
		_objects.push_back(go);
		ecsSEND_MESSAGE(go, onStart);
		staticLayer->addChild(go->getComponent<SceneNode>()->getNode());
	}

	return true;
}
